/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.core.bridge
{
	import flash.display.DisplayObject;
	import flash.errors.IllegalOperationError;
	import flash.events.Event;
	import flash.system.ApplicationDomain;
	
	import org.etherframework.IEther;
	import org.etherframework.common.Access;
	import org.etherframework.common.DomainAccess;
	import org.etherframework.common.IAccessable;
	import org.etherframework.common.IDestroyable;
	import org.etherframework.common.IDetachable;
	import org.etherframework.common.IStatefulDestroyable;
	import org.etherframework.common.wire.IWirable;
	import org.etherframework.core.EtherConfig;
	import org.etherframework.core.IModule;
	import org.etherframework.core.common.Ether;
	import org.etherframework.core.common.IArgumentFactory;
	import org.etherframework.core.common.IControllable;
	import org.etherframework.core.common.IControllerFactory;
	import org.etherframework.core.context.Context;
	import org.etherframework.core.context.IContexts;
	import org.etherframework.core.events.InteractionEvent;
	import org.etherframework.core.manager.DestructionManager;
	import org.etherframework.core.reflection.Parameter;
	import org.etherframework.core.utils.AccessValidator;
	import org.etherframework.core.utils.Dispatcher;
	
	[ExcludeClass]
	public final class Bridge implements IBridge, IControllerFactory, IArgumentFactory, 
		IStatefulDestroyable, IDetachable
	{
		private var _access:String;
		private var _domain:String;
		private var _config:BridgeConfig;
		private var _destroyed:Boolean = false;
		private const _thisArgumentFactory:IArgumentFactory = 
			new ArgumentFactory(Vector.<IArgumentFactory>([this]));

		public function initialize(config:BridgeConfig):void
		{
			if (!config)
				throw new ArgumentError("Argument config:BridgeConfig can not be null.");
			
			_config = config;
			_config.dispatcher.interceptor = eventInterceptor;
			_access = config.config.defaultAccess;
			_domain = config.config.defaultDomain;
			
			accessChanged();
		}
		
		public function createEther(context:Context = null):IEther
		{
			return new Ether(this, context || _config.defaultContext);
		}
		
		public function hasController(target:Object):IObjectController
		{
			return _config.controllers.hasController(target); 
		}
		
		public function process(target:Object, ether:IEther = null, 
								controllerFactory:IControllerFactory = null, 
								argumentFactory:IArgumentFactory = null):Object
		{
			if (!target)
				throw new ArgumentError("Argument target:Object can not be null.");
			if (_config.controllers.hasController(target))
				throw new IllegalOperationError("Object " + target + 
					" already has controller.");
			
			if (!ether)
				ether = createEther();
			
			const argFactory:IArgumentFactory = argumentFactory ?  
				new ArgumentFactory(new <IArgumentFactory>[argumentFactory, this]) : 
				_thisArgumentFactory;
			
			const instance:Object = target is Class ? 
				_config.processor.processClass(target as Class, ether, argFactory) : 
				_config.processor.processInstance(target, ether);
			
			// TODO: Think about creating controllers for all processed 
			// objects but not only for IControllable.
			if (instance is IControllable)
			{
				const controller:IObjectController = _config.controllers.createController(
					instance, ether, controllerFactory || this);
				controller.initialize();
				return controller;
			}
			
			if (instance is IWirable)
				_config.facade.wire(instance as IWirable, ether);
			
			return instance; 
		}
		
		public function destruct(value:Object):void
		{
			if (!value)
				throw new ArgumentError("Argument value:Object can not be null.");
			if (value is IModule)
				throw new IllegalOperationError("Only IEtherManager can be used to destroy IModule.");

			var controller:IObjectController = _config.controllers.hasController(value);
			
			if (!controller)
				throw new IllegalOperationError("Object " + value + " is not registered.");
			
			_config.destructionManager.destroy(controller, destructionHandler);
		}
		
		public function destructionHandler(item:IObjectController):void
		{
			
		}
		
		private function eventInterceptor(e:InteractionEvent):Event
		{
			// TODO: Think about this approach.
			// If module is detached stop any event propagation.
			
			// If it is a local dispatch then just pass through.
			if (e.target == _config.localDispatcher)
				return e.data as Event;
			
			// If it is a global dispatch and domain of accepting
			// side is single then application domains should be equal.
			if ((_domain == DomainAccess.SINGLE) && 
				(e.initiator.applicationDomain != _config.applicationDomain))
				return null;
			
			return e.data as Event;
		}
		
		public function createArgument(parameter:Parameter, context:Context):Object
		{
			return _config.facade.contextual(context).dependencies.getInstance(parameter.type as Class);
		}
		
		public function createController(target:Object, ether:IEther,  bridge:IBridge):IObjectController
		{
			return new ObjectController(target, ether, bridge.facade);
		}
		
		public function attach():void
		{
			if (_config.facade is IDetachable)
				IDetachable(_config.facade).attach();
		}
		
		public function detach():void
		{
			if (_config.facade is IDetachable)
				IDetachable(_config.facade).detach();
		}
		
		public function destroy():void
		{
			if (_config.facade is IDestroyable)
				IDestroyable(_config.facade).destroy();
			
			if (_config.contexts is IDestroyable)
				IDestroyable(_config.contexts).destroy();
			
			_config.controllers.destroy();
		}
		
		public function get access():String { return _access; }
		public function set access(value:String):void 
		{
			if (_access == value)
				return;
			
			AccessValidator.validateAccess(value);
			_access = value;
			accessChanged();
		}
		
		public function get domain():String { return _domain; }
		public function set domain(value:String):void 
		{ 
			if (_domain == value)
				return;
			
			AccessValidator.validateDomain(value);
			_domain = value;
			
			if (_config.facade is IAccessable)
				IAccessable(_config.facade).domain = value;
		}
		
		private function accessChanged():void
		{
			if (_access == Access.LOCAL && 
				_config.dispatcher.source != _config.localDispatcher)
			{
				_config.dispatcher.switchSource(_config.localDispatcher);
			}
			else if (_access != Access.LOCAL && 
				_config.dispatcher.source != _config.globalDispatcher)
			{
				_config.dispatcher.switchSource(_config.globalDispatcher);
			}
			
			if (_config.facade is IAccessable)
				IAccessable(_config.facade).access = _access;
		}
		
		public function get facade():IBridgeFacades { return _config.facade; }
		public function get config():EtherConfig { return _config.config; }
		public function get applicationDomain():ApplicationDomain { return _config.applicationDomain; }
		public function get interaction():IInteraction { return _config.interaction; }
		public function get dispatcher():Dispatcher {return _config.dispatcher};
		public function get destroyed():Boolean { return _destroyed; }
		public function set destroyed(value:Boolean):void { _destroyed = value; }
		public function get view():DisplayObject { return _config.module.view; }
		public function get contexts():IContexts { return _config.contexts; }
		public function get destructionManager():DestructionManager { return _config.destructionManager; }
		public function get controllers():Controllers { return _config.controllers; }
	}
}